// Amf3Output.cpp: implementation of the Amf3Output class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"

#ifndef _WIN32
#include "Amf_XML.h"
#include <fstream>
#import "MSXML4.dll"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

namespace Amf{
	/**
	*	Convert byte array to HEX string
	* @param pInData header address of byte array.
	* @param nInLen length of byte array.
	* @param pOutHexStr buffer for output Hex string, the length mast greater then (nInLen * 2)
	*/
	static void CharToHex (LPCTSTR pInData, unsigned int nInLen,
		char* pOutHexStr /*nInLen*2 + 1*/)
	{
		unsigned char * c = (unsigned char *)pInData;
		for (unsigned int i=0; i<nInLen; i++)
		{
			unsigned char ch = *c++;
			unsigned char lo = ch & 0xF;
			unsigned char hi = ch >> 4;
			*pOutHexStr ++ = hi > 9 ? ('A' + hi - 10) : ('0' + hi);
			*pOutHexStr ++ = lo > 9 ? ('A' + lo - 10) : ('0' + lo);
		}
	}

	/**
	*	Convert HEX string to byte array
	*	@param pInHexStr the header address of Hex string .
	*	@param nInLen the lenght of Hex string.
	*	@param pOutData buffer for output byte array. the length mast greater then (nInLen / 2)
	*	@reutrn length of byte array converted.
	*/
	static int HexToChar (LPCTSTR pInHexStr, unsigned int nInLen,
		char* pOutData /*nInLen/2*/)
	{
		const char *p = pInHexStr;
		const char *pend = p + nInLen;
		char *pout = pOutData;
		unsigned char c = 0, d = 0;
		bool get_first = false;

		while (p != pend)
		{
			switch (*p ++)
			{
			case '0': c = 0; break;
			case '1': c = 1; break;
			case '2': c = 2; break;
			case '3': c = 3; break;
			case '4': c = 4; break;
			case '5': c = 5; break;
			case '6': c = 6; break;
			case '7': c = 7; break;
			case '8': c = 8; break;
			case '9': c = 9; break;
			case 'a': case 'A': c = 10; break;
			case 'b': case 'B': c = 11; break;
			case 'c': case 'C': c = 12; break;
			case 'd': case 'D': c = 13; break;
			case 'e': case 'E': c = 14; break;
			case 'f': case 'F': c = 15; break;
			default:
				return -1;
			}

			if (get_first)
				*pout ++ = (d << 4) + c;
			else
				d = c;
			get_first = !get_first;
		}

		return (pout - pOutData);
	}

	AmfObjectPtr Amf3XML::readObject(LPCTSTR strFileName)
	{
		MSXML2::IXMLDOMDocumentPtr plDomDocument;
		HRESULT hr = plDomDocument.CreateInstance(__uuidof(MSXML2::DOMDocument40));
		if(FAILED(hr))
		{
			_com_error er(hr);
			m_strErrorDesc.printf("create XMLDOMDocument instance failed.");
			return AmfObject::GetNull();
		}

		_variant_t	varOut((bool)TRUE);
		varOut = plDomDocument->load(strFileName);

		if((bool)varOut == FALSE)
		{
			MSXML2::IXMLDOMParseErrorPtr errPtr = plDomDocument->GetparseError();  
			_bstr_t bstrErr(errPtr->reason);

			m_strErrorDesc.printf( "load XML Error At Row[%d] Col[%d]. Error='%s'.\n",
				errPtr->line, errPtr->linepos,
				(char*)bstrErr);
			return AmfObject::GetNull();
		}

		MSXML2::IXMLDOMElementPtr pDocRoot = plDomDocument->documentElement;
		if (NULL == pDocRoot)
		{
			m_strErrorDesc.printf("config.xml error!\n");
			return AmfObject::GetNull();
		}
		return readObjectImpl((MSXML2::IXMLDOMNode *)pDocRoot->firstChild);
	}

	AmfObjectPtr Amf3XML::readObject (std::istream& in)
	{
		m_strErrorDesc.clear();

		MSXML2::IXMLDOMDocumentPtr plDomDocument;
		HRESULT hr = plDomDocument.CreateInstance(__uuidof(MSXML2::DOMDocument40));
		if(FAILED(hr))
		{
			_com_error er(hr);
			m_strErrorDesc.printf("create XMLDOMDocument instance failed.");
			return AmfObject::GetNull();
		}

		std::stringstream strTmp;
		in >> strTmp.rdbuf();

		_variant_t	varOut((bool)TRUE);
		varOut = plDomDocument->loadXML(strTmp.str().c_str());
		if((bool)varOut == FALSE)
		{
			MSXML2::IXMLDOMParseErrorPtr errPtr = plDomDocument->GetparseError();  
			_bstr_t bstrErr(errPtr->reason);

			m_strErrorDesc.printf( "load XML Error At Row[%d] Col[%d]. Error='%s'.\n",
				errPtr->line, errPtr->linepos,
				(char*)bstrErr);
			return AmfObject::GetNull();
		}

		MSXML2::IXMLDOMElementPtr pDocRoot = plDomDocument->documentElement;
		if (NULL == pDocRoot)
		{
			m_strErrorDesc.printf("config.xml error!\n");
			return AmfObject::GetNull();
		}
		return readObjectImpl((MSXML2::IXMLDOMNode *)pDocRoot->firstChild);
	}

	AmfObjectPtr Amf3XML::readObjectImpl(void *root)
	{
		MSXML2::IXMLDOMElementPtr pXML = (MSXML2::IXMLDOMNode *)(root);
		StdString name = pXML->nodeName;
		int len = name.length();
		if (len < 4)
			return AmfObject::GetNull();
		char type = name[len - 1];

		switch (type)
		{
		case 'N':	// Number
			{
				double val = atof (pXML->text);
				int32 v_i = (int32)val;
				if (v_i == val)
				{// auto convert to int32
					AmfIntPtr pInt (HP_NEW);
					pInt->SetValue(v_i);
					return pInt;
				}
				else
				{// convert to double
					AmfDoublePtr pDouble (HP_NEW);
					pDouble->SetValue(val);
					return pDouble;
				}
			}
			break;
		case 'S':	// String
			{
				AmfStringPtr pString (HP_NEW);
				pString->SetValueA(pXML->text);
				return pString;
			}
			break;
		case 'A':	// Array
			{
				AmfArrayPtr pArr(HP_NEW);
				MSXML2::IXMLDOMNodePtr child = pXML->firstChild;

				while (NULL != child)
				{
					if(MSXML2::NODE_ELEMENT == child->nodeType)
					{
						AmfObjectPtr p_obj = readObjectImpl((MSXML2::IXMLDOMNode *)child);
						if (!p_obj->IsNull())
							pArr->AddData(p_obj);
					}
					child = child->nextSibling;
				}
				return pArr;
			}
			break;
		case 'B':	// Boolean
			{
				AmfBoolPtr pBool (HP_NEW);
				pBool->SetValue(strcmp(pXML->text, "true") == 0);
				return pBool;
			}
			break;
		case 'O':	// Object
			{
				AmfAsObjectPtr pAsObj(HP_NEW);
				MSXML2::IXMLDOMNodePtr child = pXML->firstChild;
				
				while (NULL != child)
				{
					if(MSXML2::NODE_ELEMENT == child->nodeType)
					{
						MSXML2::IXMLDOMElementPtr pElement = child;
						AmfObjectPtr p_obj = readObjectImpl((MSXML2::IXMLDOMElement *)pElement);
						if (!p_obj->IsNull())
						{
							_variant_t	varOut = pElement->getAttribute("name");
							pAsObj->AddData(varOut.bstrVal, p_obj);
						}
					}

					child = child->nextSibling;
				}

				return pAsObj;
			}
			break;
		case 'T':	// Datetime
			{//
				AmfDatePtr pDate (HP_NEW);
				pDate->SetValue(AToW(pXML->text));
				return pDate;
			}
			break;
		case 'X':	// XML
			break;
		case 'C':	// ByteArray
			{
				AmfByteArrayPtr pByteArray (HP_NEW);
				StdByteArray &bytes = pByteArray->GetValue();
				_bstr_t str = pXML->Gettext();
				int len = str.length();
				if (len > 1)
				{
					bytes.resize(len / 2);
					int rc = HexToChar (str, len, (char *)bytes.pbegin());
					if (rc == -1)
						bytes.clear();
					else
						bytes.resize(rc);
				}
				return pByteArray;
			}
			break;
		}

		return AmfObject::GetNull();
	}

	bool Amf3XML::writeObject(LPCTSTR strFileName, AmfObjectPtr obj)
	{
		m_strErrorDesc.clear();

		MSXML2::IXMLDOMDocumentPtr plDomDocument;
		HRESULT hr = plDomDocument.CreateInstance(__uuidof(MSXML2::DOMDocument40));
		if(FAILED(hr))
		{
			_com_error er(hr);
			m_strErrorDesc.printf("create XMLDOMDocument instance failed.");
			return false;
		}

		LPCTSTR root_text = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"\
			"<AM:ROOT xmlns:AM=\"AMFObject/*\" > </AM:ROOT>";
		plDomDocument->loadXML(root_text);

		MSXML2::IXMLDOMElementPtr pRoot = plDomDocument->documentElement;
		writeObjectImpl(obj, (MSXML2::IXMLDOMDocument*)plDomDocument, (MSXML2::IXMLDOMElement*)pRoot);

		hr = plDomDocument->save(strFileName);
		if(FAILED(hr))
		{
			return false;
		}

		return true;
	}

	bool Amf3XML::writeObject (std::ostream& out, AmfObjectPtr obj)
	{
		m_strErrorDesc.clear();

		MSXML2::IXMLDOMDocumentPtr plDomDocument;
		HRESULT hr = plDomDocument.CreateInstance(__uuidof(MSXML2::DOMDocument40));
		if(FAILED(hr))
		{
			_com_error er(hr);
			m_strErrorDesc.printf("create XMLDOMDocument instance failed.");
			return false;
		}

		LPCTSTR root_text = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"\
			"<AM:ROOT xmlns:AM=\"AMFObject/*\" > </AM:ROOT>";
		plDomDocument->loadXML(root_text);

		MSXML2::IXMLDOMElementPtr pRoot = plDomDocument->documentElement;
		writeObjectImpl(obj, (MSXML2::IXMLDOMDocument*)plDomDocument, (MSXML2::IXMLDOMElement*)pRoot);

		_bstr_t str = plDomDocument->Getxml();

		out << str;
		return out.good();
	}

	void* Amf3XML::writeObjectImpl(AmfObjectPtr obj, void* pDoc, void* pRoot, LPCTSTR strName)
	{
		if (obj == NULL || obj->IsNull() || pDoc == NULL || pRoot == NULL)
			return NULL;

		MSXML2::IXMLDOMDocumentPtr plDomDocument = (MSXML2::IXMLDOMDocument*)pDoc;
		MSXML2::IXMLDOMElementPtr pDomRoot = (MSXML2::IXMLDOMElement* )pRoot;

		int32 iType = ((AmfObjectPtr)obj)->DataType();
		switch(iType) {
		case AMF3_STRING:
			{
				MSXML2::IXMLDOMElementPtr pEle = plDomDocument->createElement("AM:S");
				if(pEle != NULL)
				{
					if(strName != NULL)
						pEle->setAttribute("name", strName);

					MSXML2::IXMLDOMCDATASectionPtr pData = plDomDocument->createCDATASection(WToA(obj->ToString().c_str()).c_str());
					pEle->appendChild(pData);
					//pEle->text = WToA(obj->ToString().c_str()).c_str();
				}
				pDomRoot->appendChild(pEle);
				return pEle;
			}
		case AMF3_INTEGER:
		case AMF3_NUMBER:
			{
				MSXML2::IXMLDOMElementPtr pEle = plDomDocument->createElement("AM:N");
				if(pEle != NULL)
				{
					if(strName != NULL)
						pEle->setAttribute("name", strName);

					pEle->text = WToA(obj->ToString().c_str()).c_str();
				}
				pDomRoot->appendChild(pEle);
				return pEle;
			}
			break;
		case AMF3_TRUE:
		case AMF3_FALSE:
			{
				MSXML2::IXMLDOMElementPtr pEle = plDomDocument->createElement("AM:B");
				pDomRoot->appendChild(pEle);
				if(pEle != NULL)
				{
					if(strName != NULL)
						pEle->setAttribute("name", strName);

					pEle->text = (obj->ToInt() == 1) ? "true" : "false";
				}
				return pEle;
			}
			break;
		case AMF3_DATE:
			{
				MSXML2::IXMLDOMElementPtr pEle = plDomDocument->createElement("AM:T");
				if(pEle != NULL)
				{
					if(strName != NULL)
						pEle->setAttribute("name", strName);

					pEle->text = WToA(obj->ToString().c_str()).c_str();
				}
				pDomRoot->appendChild(pEle);
				return pEle;
			}
			break;
		case AMF3_XML:
			break;
		case AMF3_XMLSTRING:
			break;
		case AMF3_UNDEFINED:
			break;
		case AMF3_ARRAY:
			{
				MSXML2::IXMLDOMElementPtr pEle = plDomDocument->createElement("AM:A");
				if(pEle != NULL)
				{
					if(strName != NULL)
						pEle->setAttribute("name", strName);

					pDomRoot->appendChild(pEle);
					int n = obj->GetChildNumber();
					pEle->setAttribute("n", n);
					for (int i=0; i<n; i++)
					{
						AmfObjectPtr child = obj->Item(i);
						writeObjectImpl(child, (MSXML2::IXMLDOMDocument*)plDomDocument, (MSXML2::IXMLDOMElement*)pEle);
					}
				}
				return pEle;
			}
			break;
		case AMF3_OBJECT:
			{
				MSXML2::IXMLDOMElementPtr pEle = plDomDocument->createElement("AM:O");
				if(pEle != NULL)
				{
					if(strName != NULL)
						pEle->setAttribute("name", strName);

					pDomRoot->appendChild(pEle);
					int n = obj->GetChildNumber();
					pEle->setAttribute("n", n);
					AmfIteratorPtr it = obj->GetChildIterator();
					for (; !it->Eof(); it->MoveNext())
					{
						AmfObjectPtr child = it->Get();
						writeObjectImpl(child,(MSXML2::IXMLDOMDocument*)plDomDocument, 
								(MSXML2::IXMLDOMElement*)pEle, Amf::WToA(it->GetKey().c_str()).c_str());
					}
				}
				return pEle;
			}
			break;
		case AMF3_BYTEARRAY:
			{
				MSXML2::IXMLDOMElementPtr pEle = plDomDocument->createElement("AM:C");
				if(pEle != NULL)
				{
					if(strName != NULL)
						pEle->setAttribute("name", strName);

					pDomRoot->appendChild(pEle);
					int n = obj->GetChildNumber();
					pEle->setAttribute("n", n);
					AmfByteArrayPtr pByteArray = obj;
					if (NULL == pByteArray)
					{
						assert (NULL != pByteArray);
					}

					if (m_bWriteBinary)
					{
						StdByteArray &bytes = pByteArray->GetValue();
						if (bytes.size() > 0)
						{
							StdString buf;
							buf.resize (bytes.size() * 2);
							CharToHex ((LPCTSTR)bytes.pbegin(), bytes.size(), (char *)buf.c_str());

							pEle->text = buf.c_str();
						}
					}
					else
					{
						MSXML2::IXMLDOMCommentPtr pCom = plDomDocument->createComment("Byte Array");
						pEle->appendChild(pCom);
					}

				}
				return pEle;
			}
			break;

		default:;
		}

		return NULL;
	}

} //namespace Amf
#endif // _WIN32